package org.xtext.runtime.term;

import java.util.Map;

import org.xtext.runtime.type.IType;
import org.xtext.runtime.type.TypeException;
import org.xtext.runtime.util.Bindings;
import org.xtext.runtime.util.ILogicVisitor;


public class Variable implements ITerm {
	private String identifier;
	private ITerm value;
	private IType type;
	
	public Variable(IType type, String identifier) {
		this(type, identifier, null);
	}
	
	protected Variable(IType type, String identifier, ITerm value) {
		this.type = type;
		this.identifier = identifier;
		this.value = value;
	}
	
	public IType type() {
		return type;
	}
	
	public void assign(ITerm term) throws TypeException {
		if (term == null) {
			System.out.println("assigning null");
		} else if (term.type() == null) {
			System.out.println("type is null");
		}
		if (term.type().matches(type)) {
			value = term;
		} else {
			throw new TypeException("Type Mismatch: expected " + type + " but got " + term.type() + " for " + identifier);
		}
	}
	
	public String identifier() {
		return identifier;
	}
	
	public ITerm value() {
		return value;
	}

	public String toString() {
		return type.toString() + " " + identifier() + " / " + value;
	}
	
	public Variable copy() {
		if (value != null) {
			return new Variable(type, identifier, value.copy());
		}
		
		return new Variable(type, identifier);
	}

	public boolean isFree() {
		return value == null;
	}

	public void clear() {
		value = null;
	}
	
	public boolean equals(Object object) {
		if (object instanceof Variable) {
			Variable variable = (Variable) object;
			return identifier().equals(variable.identifier());
		}
		return false;
	}

	public void applyRenaming(String prefix) {
		identifier = prefix + identifier;
	}

	public static ITerm rawValue(ITerm term, Map<String, Variable> vars) {
		ITerm temp = term;
		if (temp instanceof Variable) {
			Variable v = (Variable) temp;
			if (v.isFree()) {
				temp = vars.get(v.identifier());
				return temp;
			} else {
				temp = v.value();
			}

			if (temp == null) temp = term;
		}
		return temp.copy(vars);
	}

	public static ITerm rawValue(ITerm term) {
		ITerm temp = term;
		if (temp instanceof Variable) {
			temp = ((Variable) temp).value();
			if (temp == null) temp = term;
		}
		return temp;
	}

	@Override
	public Variable copy(Map<String, Variable> vars) {
		Variable var = vars.get(identifier);
		if (var == null) {
//			System.out.println("copy called on new Variable: " + this);
			vars.put(identifier, var=this.copy());
		}
		return var;
	}
	
	public boolean hasFreeVariables() {
		return value == null;
	}

	@Override
	public ITerm apply(Bindings bindings) {
		if (this.isFree()) {
			ITerm term = bindings.getStoredValue(this);
			if (term != null)
				try {
					assign(term);
				} catch (TypeException e) {
					e.printStackTrace();
				}
		}
		return this;
	}
	
	@Override
	public Object accept(ILogicVisitor visitor) {
		return visitor.visit(this);
	}
}
